﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin;

namespace Framework.Owin.WebApiSecurity
{
    /// <summary>
    /// 统计IP的总访问次数
    /// </summary>
    public class IPLimitMiddleware : OwinMiddleware
    {
        private static object lockobj = new object();

        /// <summary>
        /// 下一个“中间件”对象
        /// </summary>
        private OwinMiddleware _next;

        private string _path;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="next"></param>
        /// <param name="getIpListfFunc"></param>
        /// <param name="setIpListAction"></param>
        /// <param name="getIpBlackListFunc"></param>
        /// <param name="setIpBlackListAction"></param>
        /// <param name="delIpListAction"></param>
        /// <param name="delIpBlackAction"></param>
        public IPLimitMiddleware(OwinMiddleware next,
            Func<string, List<VisitLog>> getIpListfFunc,
            Action<string, List<VisitLog>> setIpListAction,
            Action<string> delIpListAction,
            Func<string, IpBlack> getIpBlackListFunc,
            Action<string, IpBlack> setIpBlackListAction,
            Action<string> delIpBlackAction)
            : base(next)
        {
            _next = next;

            this._getIpListfFunc = getIpListfFunc;
            this._setIpListAction = setIpListAction;
            this._getIpBlackListFunc = getIpBlackListFunc;
            this._setIpBlackListAction = setIpBlackListAction;
            _delIpBlackAction = delIpBlackAction;
            this._delIpListAction = delIpListAction;
        }

        /// <summary>
        /// 得到指定ip访问列表
        /// </summary>
        private readonly Func<string, List<VisitLog>> _getIpListfFunc;

        /// <summary>
        /// 添加ip访问记录
        /// 参数当然ip所有访问记录
        /// </summary>
        private readonly Action<string, List<VisitLog>> _setIpListAction;

        /// <summary>
        /// 删除ip所有访问记录
        /// </summary>
        private readonly Action<string> _delIpListAction;

        /// <summary>
        /// ip 黑名单
        /// </summary>
        private readonly Func<string, IpBlack> _getIpBlackListFunc;

        private readonly Action<string, IpBlack> _setIpBlackListAction;

        private readonly Action<string> _delIpBlackAction;

        /// <summary>
        /// ip 黑名单处理
        /// </summary>
        public Action<IOwinContext> IpBlackListProceAction;

        /// <summary>
        /// 访问限制时间 单位（次/秒）
        /// </summary>
        private const int LimitTime = 30;

        /// <summary>
        /// 一分钟内访问最大次数
        /// </summary>
        private const int VisitMaxCount = 100;

        public override Task Invoke(IOwinContext context)
        {
            lock (lockobj)
            {
                //获取请求者的IP地址
                var currentIp = context.Request.RemoteIpAddress;

                //获取请求的Url地址
                var currentUrl = context.Request.Uri.ToString();

                #region 黑名单

                var ipBlack = _getIpBlackListFunc(currentIp);
                if (ipBlack != null)
                {
                    var limitTime = ipBlack.LimitStartTime.AddSeconds(ipBlack.LimitTime);
                    //当时Ip处于黑名单时处理
                    //注:当处于黑名单中，每次多访问一次就多加30秒限制
                    if (DateTime.Compare(DateTime.Now, limitTime) < 0)
                    {
                        ipBlack.LimitTime += LimitTime;
                        _setIpBlackListAction(currentIp, ipBlack);
                        //默认黑名单处理
                        if (IpBlackListProceAction == null)
                        {
                            const string outString = "访问频繁，已被限制访问，稍后重试";
                            var outBytes = Encoding.UTF8.GetBytes(outString);
                            context.Response.ContentType = "text/html; charset=utf-8";
                            context.Response.Write(outBytes, 0, outBytes.Length);
                        }
                        else
                        {
                            IpBlackListProceAction(context);
                        }
                        return Task.FromResult<int>(0);

                    }
                    _delIpBlackAction(currentIp);
                }

                #endregion

                //得到当前ip所有访问记录
                var value = _getIpListfFunc(currentIp) ?? new List<VisitLog>();
                if (value.Any())
                {
                    #region 当前ip有访问记录时处理过程

                    var list = value.Where(p => p.Url == currentUrl).ToList();
                    //当前url记录
                    if (list.Count > 0)
                    {
                        //取出最早的一条时间记录，与当前时间对比，如果大于等于1分钟，那么清除整个列表
                        var minDateTime = list.Where(t => t.Url == currentUrl).Min(t => t.VisitDateTime);

                        var ts = DateTime.Now - minDateTime;
                        if (ts.Minutes >= 1)
                        {
                            //大于等于1分钟，清空当前Url的访问记录
                            value.RemoveAll(r => r.Url == currentUrl);
                        }
                        else
                        {
                            //1分钟内访问次数大于等于n次就加到缓存黑名单中，5分钟内不允许再访问
                            var currentCount = value.Count(t => t.Url == currentUrl);
                            //一分钟大于最大访问数
                            if (currentCount >= VisitMaxCount)
                            {
                                ipBlack = new IpBlack
                                {
                                    Ip = currentIp,
                                    LimitStartTime = DateTime.Now,
                                    LimitTime = VisitMaxCount * LimitTime
                                };
                                _setIpBlackListAction(currentIp, ipBlack);
                                //IP加入到黑名单后，删除此IP的全部访问记录
                                _delIpListAction(currentIp);
                                return _next.Invoke(context);
                            }
                        }
                    }

                    //添加访问记录到列表中
                    var visitLog = new VisitLog
                    {
                        Url = currentUrl,
                        VisitDateTime = DateTime.Now
                    };
                    value.Add(visitLog);
                    _setIpListAction(currentIp, value);
                    #endregion

                }
                else
                {
                    //首次访问，创建相应的对象
                    var visitLog = new VisitLog
                    {
                        Url = currentUrl,
                        VisitDateTime = DateTime.Now
                    };
                    value.Add(visitLog);
                    _setIpListAction(currentIp, value);
                }
                return _next.Invoke(context);
            }
        }
    }
    //public static class HttpRequestMessageExtensions
    //{
    //    private const string HttpContext = "MS_HttpContext";
    //    private const string RemoteEndpointMessage =
    //     "System.ServiceModel.Channels.RemoteEndpointMessageProperty";
    //    private const string OwinContext = "MS_OwinContext";
    //    public static string GetClientIpAddress(this HttpRequestMessage request)
    //    {
    //        // Web-hosting. Needs reference to System.Web.dll
    //        if (request.Properties.ContainsKey(HttpContext))
    //        {
    //            dynamic ctx = request.Properties[HttpContext];
    //            if (ctx != null)
    //            {
    //                return ctx.Request.UserHostAddress;
    //            }
    //        }
    //        // Self-hosting. Needs reference to System.ServiceModel.dll. 
    //        if (request.Properties.ContainsKey(RemoteEndpointMessage))
    //        {
    //            dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage];
    //            if (remoteEndpoint != null)
    //            {
    //                return remoteEndpoint.Address;
    //            }
    //        }
    //        // Self-hosting using Owin. Needs reference to Microsoft.Owin.dll. 
    //        if (request.Properties.ContainsKey(OwinContext))
    //        {
    //            dynamic owinContext = request.Properties[OwinContext];
    //            if (owinContext != null)
    //            {
    //                return owinContext.Request.RemoteIpAddress;
    //            }
    //        }
    //        return null;
    //    }
    //}
    /// <summary>
    /// IP访问日志
    /// </summary>
    public class VisitLog
    {
        public string Url { get; set; }
        public DateTime VisitDateTime { get; set; }
    }

    /// <summary>
    /// IP黑名单
    /// </summary>
    public class IpBlack
    {
        /// <summary>
        /// ip地址
        /// </summary>
        public string Ip { get; set; }

        /// <summary>
        /// 限制开始时间
        /// </summary>
        public DateTime LimitStartTime { get; set; }

        /// <summary>
        /// 限制时间 秒
        /// </summary>
        public int LimitTime { get; set; }
    }
}
